12.6 Synchrone und asynchrone Operationen
 
Bisher haben wir immer nur synchrone Operationen mit den Datenströmen ausgeführt. Bei einer synchronen Operation kann eine Anwendung erst dann weiterlaufen, wenn alle Lese- und Schreibzugriffe abgeschlossen sind. Häufig kommt es aber vor, dass ein Lese- oder Schreibvorgang längere Zeit in Anspruch nimmt. Dann sollte der Zugriff asynchron erfolgen, damit der Anwender mit der Anwendung weiterarbeiten kann, während die zeitaufwändige Operation in einem zweiten Thread parallel ausgeführt wird.
Nicht alle Klassen des Namespace System.IO ermöglichen asynchrone Operationen, sondern nur diejenigen, die von der Klasse Stream abgeleitet sind. Dazu zählt zum Beispiel auch die Klasse FileStream. Bereits bei der Instanziierung eines Stream-Objekts wird durch die Wahl eines passenden Konstruktors festgelegt, ob das Objekt asynchrone Operationen unterstützen soll.
| public FileStream (string, FileMode, FileAccess, FileShare, int, bool);
|
Ausschlaggebend ist der letzte Parameter vom Typ bool. Wird dem Parameter true übergeben, wird eine asynchrone Ein- bzw. Ausgabe ermöglicht, die bei den anderen Konstruktoren standardmäßig auf false gesetzt ist.
Eingeleitet werden die E/A-Operationen mit BeginWrite bzw. BeginRead, die Sie nun benutzen können.
| public override IAsyncResult BeginRead(byte[], int, int,
|
| AsyncCallback, object);
|
| public override IAsyncResult BeginWrite(byte[], int, int,
|
| AsyncCallback, object);
|
Bei deren Aufruf wird der Schreib- bzw. Lesevorgang in einem anderen Thread ausgeführt, während die Operationen im initiierenden Hauptthread weiter ausgeführt werden. Dem ersten Parameter werden die zu schreibenden Daten als Array übergeben, dem zweiten die Position, ab der die Daten geschrieben werden sollen, und dem dritten die Anzahl der zu schreibenden Bytes. Beide Methoden bieten darüber hinaus die Option, der Parameterliste einen Delegaten vom Typ AsyncCallback zu übergeben, der die zurückzurufende Methode im Hauptthread beschreibt. Der letzte Parameter schließlich dient zur Übergabe eines beliebigen Objekts. Wenn beispielsweise mehrere asynchrone Operationen parallel ausgeführt werden, können Sie über dieses Objekt die einzelnen Operationen voneinander unterscheiden.
Ist der weitere Programmablauf von der Beendigung der asynchronen Operation abhängig, können Sie den Hauptthread mit EndWrite oder EndRead blockieren, bis der asynchrone Vorgang abgeschlossen ist. Beide Methoden werden auf das FileStream-Objekt aufgerufen.
| public override void EndRead(IAsyncResult);
|
| public override void EndWrite(IAsyncResult);
|
Der Übergabeparameter ist dabei der, der von den Methoden BeginRead bzw. BeginWrite als Rückgabewert geliefert wird.
12.6.1 Beispielprogramm eines asynchronen Schreibvorgangs
 
Das folgende Beispiel zeigt, wie eine asynchrone Schreiboperation implementiert wird.
| // ------------------------------------------------------------
|
| // Beispiel: ...\Kapitel 12\AsynchroneOperation
|
| // ------------------------------------------------------------
|
| class Program {
|
| static FileStream fs;
|
| static void Main(string[] args) {
|
| // ca. 10 MByte großes Array füllen
|
| Console.Write("Array füllen ...");
|
| byte[] byteArr = new byte[10000000];
|
| Random rnd = new Random();
|
| rnd.NextBytes(byteArr);
|
| Console.WriteLine("fertig!");
|
| // Datei erzeugen
|
| string file = @"C:\TestFile.txt";
|
| Console.WriteLine("Start ... ");
|
| // FileStream initialisieren
|
| fs = new FileStream(file, FileMode.Create, FileAccess.Write,
|
| FileShare.None, 256 * 1024, true);
|
| // Festlegen der Rückrufmethode
|
| AsyncCallback delCallback = new AsyncCallback(CallbackMethod);
|
| // Asynchrone Operation starten
|
| IAsyncResult result = fs.BeginWrite(byteArr, 0, byteArr.Length,
delCallback, null);
|
| // Irgendeine andere Operation
|
| for (int i = 0; i < 100; i++)
|
| Console.WriteLine(i);
|
| // Blockieren der Programmfortführung, bis der asynchrone
|
| // Schreibvorgang beendet ist
|
| fs.EndWrite(result);
|
| // jetzt kann der Stream geschlossen werden
|
| fs.Close();
|
| File.Delete(file);
|
| Console.ReadLine();
|
| }
|
| // diese Methode wird ausgeführt, sobald der asynchrone
|
| // Schreibvorgang beendet ist
|
| public static void CallbackMethod(IAsyncResult result) {
|
| Console.WriteLine("Asynchroner Schreibvorgang beendet.");
|
| Console.WriteLine("Name der Datei: {0}", fs.Name);
|
| Console.WriteLine("Größe der Datei: {0}", fs.Length);
|
| }
|
| }
|
Zunächst deklarieren wir ein byte-Array mit einer Größe von 10.000.000 Elementen. Das entspricht ungefähr 10 MByte. Der Zufallszahlengenerator Random füllt anschließend das Array mit x-beliebigen Zahlen.
Das Array soll nun in eine Datei geschrieben werden. Den Pfad zu dieser Datei übergeben wir der Variablen file. Anschließend wird das FileStream-Objekt initialisiert, die Rückrufmethode CallbackMethod bekannt gegeben und dann mit BeginWrite die asynchrone Schreiboperation gestartet. Die Schleife hinter dem asynchronen Aufruf dient nur dazu, sich selber von der parallelen Ausführung beider Operationen überzeugen zu können.
Vor Programmende soll der FileStream ordentlich geschlossen und die erzeugte Datei darüber hinaus gelöscht werden. Beide Vorgänge setzen jedoch voraus, dass der asynchrone Schreibvorgang abgeschlossen ist. Daher ist es unabdingbar, den Hauptthtread vorher mit EndWrite zu blockieren, um das gewährleisten zu können.
Die Ausgabe könnte bei Ihnen wie in der folgenden Abbildung gezeigt aussehen.
 Hier klicken, um das Bild zu vergrößern
Abbildung 12.6 Ausgabe der asynchronen Schreiboperation |